package robocup.component.worldobjects;

import java.util.EnumMap;
import java.util.LinkedList;
import java.util.Random;

import robocup.component.ControllerNumbers;
import robocup.component.DAInamiteConstants;
import robocup.component.PConf;
import robocup.component.SConf;
import robocup.component.WorldModel;
import robocup.component.ControllerNumbers.PLAY_MODE;
import robocup.component.ControllerNumbers.TEAM;
import robocup.component.actions.Action;
import robocup.component.actions.DashAction;
import robocup.component.actions.MoveAction;
import robocup.component.actions.PointToAction;
import robocup.component.actions.TurnAction;
import robocup.component.actions.TurnNeckAction;
import robocup.component.geometry.Vektor;
import robocup.component.math.RCMath;
import robocup.component.situations.GoalkeeperSituation;
import robocup.component.speechacts.MessageFactory;
import robocup.component.speechacts.MessageFactory.MESSAGES;
import robocup.component.tactics.AbstractState;
import robocup.component.tactics.StateEvaluation;
import robocup.component.tactics.AbstractState.STATES;
import robocup.component.util.RingList;

/**
 * The WorldModel representation of a player. It provides a great amount of
 * methods for working with its data. Because players are movable, they extend
 * the behaviour of DynamicObject.
 */
public class Player
    extends DynamicObject {

  /**
   * the place on field where the player usually is
   */
  public enum ROLE {
    // default roles
    GOALIE,
    LEFT_BACK_FOUR,
    INLE_BACK_FOUR,
    INRE_BACK_FOUR,
    RIGHT_BACK_FOUR,
    LEFT_MIDFIELD,
    CENTER_MIDFIELD,
    RIGHT_MIDFIELD,
    LEFT_ATTACKER,
    CENTER_ATTACKER,
    RIGHT_ATTACKER,
    UNKNOWN,

    META_POSITION_RIGHT_WING,
    META_POSITION_LEFT_WING,
    META_POSITION_ATTACKER,
    META_POSITION_MIDFIELD,
    META_POSITION_DEFENSE;

    /**
     * Checks wether a role conatins another.
     * 
     * @param r -
     *          role
     * @return true if role is contained, true if equals
     */
    public boolean contains(final ROLE r) {

      if (r == this) {
        return true;
      }

      switch (this) {
        case META_POSITION_ATTACKER:
          return r == LEFT_ATTACKER || r == CENTER_ATTACKER || r == RIGHT_ATTACKER;

        case META_POSITION_MIDFIELD:
          return r == LEFT_MIDFIELD || r == CENTER_MIDFIELD || r == RIGHT_MIDFIELD;

        case META_POSITION_DEFENSE:
          return r == LEFT_BACK_FOUR || r == INLE_BACK_FOUR || r == INRE_BACK_FOUR || r == RIGHT_BACK_FOUR;

        case META_POSITION_LEFT_WING:
          return r == LEFT_ATTACKER || r == LEFT_MIDFIELD || r == LEFT_BACK_FOUR;

        case META_POSITION_RIGHT_WING:
          return r == RIGHT_ATTACKER || r == RIGHT_MIDFIELD || r == RIGHT_BACK_FOUR;

      }

      return false;
    }
  };

  /**
   * A Mapping from roles to numbers (index in the array + 1)
   */
  private final ROLE[] numberToRoles = new ROLE[] { ROLE.GOALIE, ROLE.LEFT_BACK_FOUR, ROLE.INLE_BACK_FOUR, ROLE.INRE_BACK_FOUR, ROLE.RIGHT_BACK_FOUR, ROLE.LEFT_MIDFIELD, ROLE.CENTER_MIDFIELD, ROLE.RIGHT_MIDFIELD, ROLE.LEFT_ATTACKER, ROLE.CENTER_ATTACKER, ROLE.RIGHT_ATTACKER };

  /**
   * what information is the last update based on
   */
  public enum UPDATE_MODE {
    INIT,
    EXPECTED,
    HEARED,
    SEEN_FULL,
    SEEN_TEAM_OWN,
    SEEN_TEAM_OTHER,
    SEEN_NOTEAM,
    MOVED,

    // SEEN + HEARED
    META_BASED_ON_INFO
  };

  private class HearInfo {

    private int      number;

    private MESSAGES messageType;

    private int      bodyCycle;

    /**
     * Creates a new HearInfo
     * 
     * @param bodyCycle
     *          the bodyCycle of the player
     * @param number
     *          the number of the heared player
     * @param messageType
     *          the type of the message
     */
    public HearInfo(
        final int bodyCycle,
        final int number,
        final MessageFactory.MESSAGES messageType) {

      this.bodyCycle = bodyCycle;
      this.number = number;
      this.messageType = messageType;
    }

    /**
     * @return the messageType.
     */
    public MESSAGES getMessageType() {

      return this.messageType;
    }

    /**
     * @param messageType
     *          The messageType to set.
     */
    public void setMessageType(final MESSAGES messageType) {

      this.messageType = messageType;
    }

    /**
     * @return the number.
     */
    public int getNumber() {

      return this.number;
    }

    /**
     * @param number
     *          The number to set.
     */
    public void setNumber(final int number) {

      this.number = number;
    }

    /**
     * @return the bodyCycle.
     */
    public int getBodyCycle() {

      return this.bodyCycle;
    }

    /**
     * @param bodyCycle
     *          The bodyCycle to set.
     */
    public void setBodyCycle(final int bodyCycle) {

      this.bodyCycle = bodyCycle;
    }
  }

  private EnumMap<UPDATE_MODE, Integer>         updateCycles         = new EnumMap<UPDATE_MODE, Integer>(
                                                                         UPDATE_MODE.class);

  /**
   * maps each state to a list of playmodes
   */
  public EnumMap<STATES, LinkedList<PLAY_MODE>> knownStates          = new EnumMap<STATES, LinkedList<PLAY_MODE>>(
                                                                         STATES.class);

  private final RingList<TEAM>                        ballPossessions      = new RingList<TEAM>(
                                                                         10);

  /**
   * The players tricot number
   */
  private int                                   number;

  /**
   * The body direction of the player.
   */
  private double                                bodyDir;

  /**
   * The neck-angle of the player.
   */
  private double                                neckAngle;

  /**
   * The point-direction of the player.
   */
  private double                                pointDirection;

  /**
   * The players current stamina. Note, that this is known only to the
   * self-agent.
   */
  private double                                stamina;

  /**
   * The players current effort value. Note, that this is known only to the
   * self-agent.
   */
  private double                                effort;

  /**
   * The players current recovery value. Note, that this is known only to the
   * self-agent.
   */
  private double                                recovery;

  /**
   * The number of cycles, the agent is anable to act after tackling.
   */
  private int                                   tackledeadCountdown  = 0;

  /**
   * Determines if this player represents me or any player.
   */
  private boolean                               isMe;

  /**
   * Is the player a teammate?
   */
  private boolean                               isFriend;

  /**
   * Is the player agoalie?
   */
  private boolean                               isGoalie             = false;

  /**
   * The players heterogeneous type.
   */
  private PConf                                 pConf;

  /**
   * Used for calculations - do not change.
   */
  private final Vektor                                dummyVektor          = new Vektor();

  /**
   * Used for calculations - do not change.
   */
  private final Vektor                                dummyVektor2         = Vektor
                                                                         .getDummyInstance();

  /**
   * Used for calculations - do not change.
   */
  private double                                dummyDouble          = 0;

  /**
   * Arm-Direction relative to body, reaches from -180 - 180 degree
   */
  private double                                armDirection         = 0;

  /**
   * The pointing distance.
   */
  private double                                armDistance          = 0;

  /**
   * The number of cycles, the arm stays at the given point.
   */
  private int                                   armExpires           = 0;

  /**
   * Is the arm movable again?
   */
  private boolean                               armMovable           = true;

  /**
   * Does the players status changed (Stamina/PlayerType)
   */
  private boolean                               statusChanged        = false;

  /**
   * The players last stamina status.
   */
  private double                                lastStaminaStatus    = 8000;

  /**
   * The players last executed state.
   */
  private STATES                                lastState            = STATES.DUMMY_STATE;

  /**
   * The number of the players role.
   */
  private int                                   roleNumber           = -1;

  /**
   * The last heared info of this player.
   */
  private final HearInfo                              lastHearedInfo       = new HearInfo(
                                                                         -1,
                                                                         -1,
                                                                         MESSAGES.KICKED_BALL);

  /**
   * The tactical device of the player. Only used for the self-agent.
   */
  private StateEvaluation                       myStateEval          = null;

  /**
   * Flag, indicating if a player was seen offside on passing.
   */
  private boolean                               wasOffsideWhenPassed = false;

  /**
   * Flag, indicating that the player executed a standard action.
   */
  private boolean                               kickedStandard       = false;
  
  private final double[] PLAYER_DEC_AFTER_N= new double[50];
  private final double[] REL_PLAYER_DIST_AFTER_N= new double[50];
  

  private static final double CATCH_SECURITY_DIST   = 0.05;

  /**
   * Specifies if an Vektor or a double angle should be returned relative to
   * head,
   * 
   */
  public enum RELATIVE {
    TO_HEAD,
    TO_BODY
  }

  /**
   * An object holding information about a player.
   * 
   * @param iCycle -
   *          time, when he was seen last (real cycle)
   * @param iBodyCycle -
   *          time, when he was seen last (body-Cycle)
   * @param iNumber -
   *          the players tricot-number
   * @param iPosAbs -
   *          the players absolute position
   * @param iSpeed -
   *          the players speed
   * @param iBodyDir -
   *          the players body direction
   * @param iNeckAngle -
   *          the players neck-angle
   * @param iStamina -
   *          the players stamina
   * @param iEffort -
   *          the players effort
   * @param iRecovery -
   *          the players recovery
   * @param iIsMe -
   *          whether it is the self-agent or not
   * @param isFriend -
   *          whether it is a teammate or not
   * @param goalie -
   *          whether it is a goalie or not
   * @param pConf -
   *          his heterogeneous attributes
   */
  public Player(
      final int iCycle,
      final int iBodyCycle,
      final int iNumber,
      final Vektor iPosAbs,
      final Vektor iSpeed,
      final double iBodyDir,
      final double iNeckAngle,
      final double iStamina,
      final double iEffort,
      final double iRecovery,
      final boolean iIsMe,
      final boolean isFriend,
      final boolean goalie,
      final PConf pConf) {

    super(iCycle, iBodyCycle, iPosAbs, iSpeed);
    this.number = iNumber;
    this.bodyDir = iBodyDir;
    this.neckAngle = iNeckAngle;
    this.stamina = iStamina;
    this.effort = iEffort;
    this.recovery = iRecovery;
    this.isMe = iIsMe;
    this.isFriend = isFriend;
    this.isGoalie = goalie;
    this.pConf = pConf;
    if (this.pConf != null) {
      PLAYER_DEC_AFTER_N[0]= 1;
      REL_PLAYER_DIST_AFTER_N[0]= 0;
      for (int i= 1; i < 50; i++) {
        PLAYER_DEC_AFTER_N[i]= PLAYER_DEC_AFTER_N[i-1] * this.pConf.PLAYER_DECAY;
        REL_PLAYER_DIST_AFTER_N[i]= REL_PLAYER_DIST_AFTER_N[i-1]+ PLAYER_DEC_AFTER_N[i-1]; 
      }      
    }
  }

  /**
   * this method creates a new player. use it, if you wan't to override the
   * attributes anyway.
   * 
   * @return a new player with default values
   */
  public static Player getDummyInstance() {

    return new Player(-1, -1, -1, new Vektor(), new Vektor(), 0, 0, 8000.0,
        1.0, 1.0, true, true, false, null);
  }

  /**
   * this method creates an array with dummy players.
   * 
   * @param number -
   *          array length
   * @return array of dummy #number players
   */
  public static Player[] getDummyInstances(final int number) {

    final Player[] player = new Player[number];
    for (int i = 0; i < player.length; i++) {
      player[i] = Player.getDummyInstance();
    }
    return player;
  }
  
  public static Player getRandomPlayer() {
    
    Random rnd= new Random();
    rnd.setSeed(System.currentTimeMillis());
    
    double delta= 0;
    PConf pconf= new PConf();
    pconf.PLAYER_SPEED_MAX= 1.2;
    pconf.STAMINA_INC_MAX= 45;
    delta= rnd.nextDouble()*0.2;
    pconf.PLAYER_DECAY=0.4+delta;
    pconf.INERTIA_MOMENT=5+delta*25;
    delta= 0;
    pconf.DASH_POWER_RATE=0.006;
    pconf.PLAYER_SIZE=0.3;
    delta= rnd.nextDouble()*0.002;
    pconf.DASH_POWER_RATE += delta;
    pconf.STAMINA_INC_MAX += delta * -10000;
    delta= rnd.nextDouble()*0.2;
    pconf.KICKABLE_MARGIN=0.7+delta;
    pconf.KICK_RAND=delta * 0.5;
    delta= rnd.nextDouble()*100;
    pconf.EXTRA_STAMINA=delta;
    pconf.EFFORT_MAX=1 + delta * -0.002;
    pconf.EFFORT_MIN=0.6 + delta * -0.002;
    pconf.PLAYER_ACCEL_MAX=1;
    SConf.getInstance();
    int number= rnd.nextInt(11)+1;
    Vektor pos= new Vektor(Vektor.XY, (rnd.nextBoolean()?rnd.nextDouble():-rnd.nextDouble())*SConf.getInstance().half_width, (rnd.nextBoolean()?rnd.nextDouble():-rnd.nextDouble())*SConf.getInstance().half_length);
    double dir= (rnd.nextDouble()*360.)-180.;
    double stam= (rnd.nextDouble()*(8000.+pconf.EXTRA_STAMINA));
    double rec= rnd.nextDouble()*0.5+0.5;
    double neck=(rnd.nextDouble()*180.)-90.;
    Vektor spd= new Vektor(rnd.nextDouble()*pconf.PLAYER_SPEED_MAX*pconf.PLAYER_DECAY, rnd.nextDouble()*360.-180.);
    double eff= rnd.nextDouble()*0.4+pconf.EFFORT_MIN;
    return new Player(0,0,number, pos, spd, dir, neck, stam, eff, rec, true, true, false, pconf);
  }

  /**
   * Clones the Player.
   * 
   * @return The Player clone
   */
  public Player cloned() {

    final Player newPlayer = Player.getDummyInstance();
    newPlayer.copy(this);
    return newPlayer;
  }

  /**
   * Copies a play into "this" player avoiding player creation ;-).
   * 
   * @param p -
   *          original player
   */
  public void copy(final Player p) {

    if (p == null) {
      return;
    }
    this.setCycle(p.getCycle());
    this.setBodyCycle(p.getBodyCycle());
    this.setNumber(p.getNumber());
    this.getPosition().x = p.getPosition().x;
    this.getPosition().y = p.getPosition().y;
    this.getSpeed().x = p.getSpeed().x;
    this.getSpeed().y = p.getSpeed().y;
    this.setBodyDir(p.getBodyDir());
    this.setNeckAngle(p.getNeckAngle());
    this.setStamina(p.getStamina());
    this.setEffort(p.getEffort());
    this.setRecovery(p.getRecovery());
    this.isMe = p.isMe;
    this.isFriend = p.isFriend;
    this.isGoalie = p.isGoalie;
    this.pConf = p.pConf;
    this.setReliable(p.isReliable());
    this.setSpeedReliable(p.speedIsReliable());
    this.updateCycles = p.updateCycles.clone();
    this.tackledeadCountdown = p.tackledeadCountdown;
    this.myStateEval = p.getMyStateEval();
    this.knownStates = p.knownStates;
    this.lastState = p.lastState;
    this.roleNumber = p.getRoleNumber();
  }

  /**
   * sets the update mode. this is used to determine when the last update of a
   * certain update occured. as this is use to tell what information of a
   * certain type arrived when there are some modes which contain other as they
   * include the other modes informational content.
   * 
   * @param mode -
   *          the update mode
   * @param bodyCycle -
   *          the body cycle of the update
   */
  public void setUpdateMode(final UPDATE_MODE mode,
      final int bodyCycle) {

    this.updateCycles.put(mode, new Integer(bodyCycle));

    // some updates contain others
    if (mode == UPDATE_MODE.SEEN_FULL) {
      this.updateCycles.put(UPDATE_MODE.SEEN_NOTEAM, new Integer(bodyCycle));
      this.updateCycles
          .put(UPDATE_MODE.SEEN_TEAM_OTHER, new Integer(bodyCycle));
      this.updateCycles.put(UPDATE_MODE.SEEN_TEAM_OWN, new Integer(bodyCycle));
    }
    else if (mode == UPDATE_MODE.SEEN_TEAM_OTHER || mode == UPDATE_MODE.SEEN_TEAM_OWN) {
      this.updateCycles.put(UPDATE_MODE.SEEN_NOTEAM, new Integer(bodyCycle));
    }
  }

  /**
   * @param mode
   *          the update mode
   * @return the last cycle with this update mode
   */
  public Integer getUpdateCycle(final UPDATE_MODE mode) {

    return this.updateCycles.get(mode);
  }

  /**
   * the age of the player depending on the update type.
   * 
   * @param currentBodyCycle
   * @param mode
   * @return the age (wm.getBodyCycle - cycle of last update of given type)
   */
  public int getAge(final int currentBodyCycle,
      final UPDATE_MODE mode) {

    if (mode == UPDATE_MODE.META_BASED_ON_INFO) {
      return Math.min(this.getAge(currentBodyCycle, UPDATE_MODE.SEEN_NOTEAM),
          this.getAge(currentBodyCycle, UPDATE_MODE.HEARED));
    }

    final Integer i = this.updateCycles.get(mode);
    if (i == null) {
      return Integer.MAX_VALUE;
    }

    if (mode == UPDATE_MODE.INIT) {
      return 0;
    }

    return currentBodyCycle - i.intValue();
  }

  /**
   * @return true if the update is based on estimation
   */
  public boolean isPredicted() {

    if (this.getUpdateCycle(UPDATE_MODE.EXPECTED) == null) {
      return false;
    }

    return this.getUpdateCycle(UPDATE_MODE.EXPECTED) > this.getBodyCycle();
  }

  /**
   * (non-Javadoc)
   * 
   * @see java.lang.Object#equals(java.lang.Object)
   */
  @Override
  public boolean equals(final Object o) {

    if (o instanceof Player) {
      final Player p = (Player) o;

      return p.number == this.number && p.isFriend == this.isFriend;
    }
    ;

    return false;

  }

  /** ****************** geometry stuff ************* */

  /**
   * Returns an absolute position denoted by the relative position of the
   * specified StaticObject <code>o</code>. The enum
   * <code>fromPosRelToHead</code> specifies if the position of the
   * StaticObject <code>o</code> is relative to the head or relative to the
   * body.
   * 
   * @param o -
   *          StaticObject
   * @param relative -
   *          specifies if the relative position of the specified StaticObject
   *          <code>o</code> is relative to the head or relative to the body
   * 
   * @return Vektor - absolute position
   */
  public final Vektor getPosAbs(final StaticObject o,
      final RELATIVE relative) {

    return this.getPosAbs(o.getPosition(), relative);
  }

  /**
   * Returns an absolute position denoted by the specified relative Vektor
   * <code>relativePosition</code>. The enum <code>relative</code>
   * specifies if the Vektor <code>relativePosition</code> is relative to the
   * head or relative to the body.
   * 
   * @param relativePosition -
   *          Vektor
   * @param relative -
   *          specifies if the Vektor <code>relativePosition</code> is
   *          relative to the head or relative to the body
   * 
   * @return Vektor - absolute position
   */
  public final Vektor getPosAbs(final Vektor relativePosition,
      final RELATIVE relative) {

    final Vektor v = relativePosition.cloned();
    if (relative == RELATIVE.TO_HEAD) {
      v.rotate(this.getHeadDir());
    }
    else if (relative == RELATIVE.TO_BODY) {
      v.rotate(this.getBodyDir());
    }
    else {
      // error
      System.out
          .println("[ERROR] Player#getPosAbs(Vektor, RELATIVE): relative=" + relative);
      return new Vektor();
    }
    return v.add(this.getPosition());
  }

  /**
   * Returns a relative position from the absolute position of the StaticObject
   * <code>o</code> either relative to the head or relative to the body. This
   * is specified by the enum <code>relative</code>.
   * 
   * @param o -
   *          StaticObject
   * @param relative -
   *          if <code>relative</code> is RELATIVE.TO_HEAD the returned Vektor
   *          will be relative to the head, if it is RELATIVE.TO_BODY the
   *          returned Vektor will be relative to the body
   * 
   * @return Vektor - position either relative to the head or relative to the
   *         body
   */
  public final Vektor getPosRel(final StaticObject o,
      final RELATIVE relative) {

    return this.getPosRel(o.getPosition(), relative);
  }

  /**
   * Returns a relative position denoted by the specified absolute Vektor
   * <code>absolutePosition</code>. The enum <code>relative</code>
   * specifies if the returned Vektor should be relative to the head or relative
   * to the body.
   * 
   * @param absolutePosition -
   *          Vektor
   * @param relative -
   *          if <code>relative</code> is RELATIVE.TO_HEAD the returned Vektor
   *          will be relative to the head, if it is RELATIVE.TO_BODY the
   *          returned Vektor will be relative to the body
   * 
   * @return Vektor - position either relative to the body or to the head
   */
  public final Vektor getPosRel(final Vektor absolutePosition,
      final RELATIVE relative) {

    final Vektor v = absolutePosition.sub(this.getPosition());
    if (relative == RELATIVE.TO_HEAD) {
      v.rotate(-this.getHeadDir());
    }
    else if (relative == RELATIVE.TO_BODY) {
      v.rotate(-this.getBodyDir());
    }
    else {
      // error
      System.out
          .println("[ERROR] Player#getPosRel(Vektor, RELATIVE): relative=" + relative);
      return new Vektor();
    }
    return v;
  }

  /**
   * Returns the absolute angle to the relative position of the specified
   * StaticObject <code>o</code>. If the position of the StaticObjct
   * <code>o</code> is relative to the head the enum <code>relative</code>
   * should be RELATIVE.TO_HEAD, else is should be RELATIVE.TO_BODY.
   * 
   * @param o -
   *          StaticObject with relative position
   * @param relative -
   *          specifies if the Vektor <code>relativePosition</code> is
   *          relative to the head or relative to the body
   * 
   * @return double - absolute angle [-180..180]
   */
  public final double getAngleAbs(final StaticObject o,
      final RELATIVE relative) {

    return this.getAngleAbs(o.getPosition(), relative);
  }

  /**
   * Returns the absolute angle to the relative Vektor
   * <code>relativePosition</code>. If the Vektor is relative to the head the
   * enum <code>relative</code> should be RELATIVE.TO_HEAD, else is should be
   * RELATIVE.TO_BODY.
   * 
   * @param relativePosition -
   *          Vektor either relative to the head or relative to the body
   * @param relative -
   *          specifies if the Vektor <code>relativePosition</code> is
   *          relative to the head or relative to the body
   * 
   * @return double - absolute angle [-180..180]
   */
  public final double getAngleAbs(final Vektor relativePosition,
      final RELATIVE relative) {

    return this.getPosAbs(relativePosition, relative).getAngle();
  }

  /**
   * Returns the relative angle from the absolute position of the StaticObject
   * <code>o</code>. If the returned angle should be relative to the head the
   * enum <code>relative</code> should be RELATIVE.TO_HEAD, else is should be
   * RELATIVE.TO_BODY.
   * 
   * @param o -
   *          StaticObject o with absolute position
   * @param relative -
   *          specifies if the returned angle will be relative to the head or
   *          relative to the body
   * 
   * @return double - relative angle either relative to the head or relative to
   *         the body [-180..180]
   */
  public final double getAngleRel(final StaticObject o,
      final RELATIVE relative) {

    return this.getAngleRel(o.getPosition(), relative);
  }

  /**
   * Returns the relative angle to the absolute Vektor
   * <code>absolutePosition</code>. If the returned Vektor should be relative
   * to the head the enum <code>relative</code> should be RELATIVE.TO_HEAD,
   * else is should be RELATIVE.TO_BODY.
   * 
   * @param absolutePosition -
   *          absolute Vektor
   * @param relative -
   *          specifies if the returned angle will be relative to the head or
   *          relative to the body
   * 
   * @return double - relative angle either relative to the head or relative to
   *         the body [-180..180]
   */
  public final double getAngleRel(final Vektor absolutePosition,
      final RELATIVE relative) {

    final double angleAbs = this.getPosition().getAngleTo(absolutePosition);
    return this.getAngleRel(angleAbs, relative);
    
  }

  /**
   * Returns the absolute angle from the specified relative angle
   * <code>relativeAngle</code>, which is either relative to the head or
   * relative to the body. This is specified by the enum <code>relative</code>.
   * 
   * @param relativeAngle -
   *          double angle either relative to the head or relative to the body
   * @param relative -
   *          specifies if the angle <code>relativeAngle</code> is relative to
   *          the head or relative to the body
   * 
   * @return double - absolute angle [-180..180]
   */
  public final double getAngleAbs(final double relativeAngle,
      final RELATIVE relative) {

    if (relative == RELATIVE.TO_HEAD) {
      return Vektor.normalize(relativeAngle + this.getHeadDir());
    }
    else if (relative == RELATIVE.TO_BODY) {
      return Vektor.normalize(relativeAngle + this.getBodyDir());
    }
    else {
      System.out.println("Player.java: relative=" + relative);
      return .0;
    }
  }

  /**
   * Returns the relative angle from the specified absolute angle
   * <code>absoluteAngle</code>. If the returned angle should be relative to
   * the head the enum <code>relative</code> should be RELATIVE.TO_HEAD, else
   * if should be RELATIVE.TO_BODY.
   * 
   * @param absoluteAngle -
   *          absolute angle
   * @param relative -
   *          specifies if the returned angle will be relative to the head or
   *          relative to the body
   * 
   * @return double - relative angle either relative to the head or relative to
   *         the body [-180..180]
   */
  public final double getAngleRel(final double absoluteAngle,
      final RELATIVE relative) {

    if (relative == RELATIVE.TO_HEAD) {
      return Vektor.normalize(absoluteAngle - this.getHeadDir());
    }
    else if (relative == RELATIVE.TO_BODY) {
      return Vektor.normalize(absoluteAngle - this.getBodyDir());
    }
    else {
      // error
      System.out
          .println("Player#getAngleRel(double, RELATIVE): relative=" + relative);
      return .0;
    }
  }

  /**
   * predicts the player in the next cycle based on an action. returns a clone.
   * 
   * @param a
   *          the action to predict
   * @return the player after the action
   */
  public Player predictPlayerAfterAction(final Action a) {

    final Player p = this.cloned();
    p.predictThisAfterAction(a);
    return p;
  }

  /**
   * predicts the player in the next cycle based on an action. working on the
   * reference!!
   * 
   * @param a
   *          the action to predict
   */
  public void predictThisAfterAction(final Action a) {

    if (a == null || (this.tackledeadCountdown > 0 && a.getType().isMain())) {
      this.extrapolate(1);
      if (this.tackledeadCountdown > 0) {
        this.tackledeadCountdown--;
      }
      this.lastState = STATES.DUMMY_STATE;
      return;
    }

    if (a.getType().isMain()) {
      this.setUpdateMode(UPDATE_MODE.EXPECTED,
          this.getUpdateCycle(UPDATE_MODE.EXPECTED) == null ? this
              .getBodyCycle() + 1 : Math.max(this
              .getUpdateCycle(UPDATE_MODE.EXPECTED), this.getBodyCycle()) + 1);
      this.lastState = STATES.DUMMY_STATE;
    }

    switch (a.getType()) {
      case CHANGE_VIEW_MODE:
        return;

      case MOVE:
        final MoveAction ma = (MoveAction) a;

        final Vektor next = new Vektor(Vektor.XY, ma.getX(), ma.getY());
        next.rotate(90);
        next.y = -next.y;
        this.setPosition(next);
        this.setSpeed(new Vektor(0, 0));
        break;

      case TURN_NECK:
        double newNeckAngle = Vektor
            .normalize(this.getNeckAngle() + ((TurnNeckAction) a).getAngle());
        if (newNeckAngle < SConf.getInstance().MINNECKANG) {
          newNeckAngle = SConf.getInstance().MINNECKANG;
        }
        else if (newNeckAngle > SConf.getInstance().MAXNECKANG) {
          newNeckAngle = SConf.getInstance().MAXNECKANG;
        }
        this.setNeckAngle(newNeckAngle);
        // no stamina increase as it is only a second action
        return;

      case DASH:
        this.predictThisAfterDashAction((DashAction) a);
        break;

      case TURN:
        this.predictThisAfterTurnAction((TurnAction) a);
        break;

      case KICK:
        this.extrapolate(1);
        break;

      case CATCH_BALL:
        this.extrapolate(1);
        return;

      case TACKLE:
        this.extrapolate(1);
        this.tackledeadCountdown = 10;
        return;

      case POINTTO:
        this.predictThisAfterPointToAction((PointToAction) a);
        break;

      case SAY:
        return;

      case ATTENTIONTO:
        return;
      default:
        System.out
            .println(this.getCycle() + " P:" + this.getNumber() + " got strange Action:" + a
                .getType());
        return;
    }

    if (!a.getType().isMain()) {
      return;
    }

    // BEGIN MT Alg. 3.1: stamina model algorithm

    // reduce recovery when stamina is below recovery decrement threshold
    if (this.stamina <= SConf.getInstance().RECOVER_DEC_THR * SConf
        .getInstance().STAMINA_MAX) {
      this.recovery -= SConf.getInstance().RECOVER_DEC;
      this.recovery = Math.max(SConf.getInstance().RECOVER_MIN, this.recovery);
    }

    // reduce effort when stamina is below effort decrement threshold
    if (this.stamina <= SConf.getInstance().EFFORT_DEC_THR * SConf
        .getInstance().STAMINA_MAX) {
      this.effort -= SConf.getInstance().EFFORT_DEC;
      this.effort = Math.max(this.getPConf().EFFORT_MIN, this.effort);
    }

    // increase effort when stamina is above effort increment threshold
    if (this.stamina >= SConf.getInstance().EFFORT_INC_THR * SConf
        .getInstance().STAMINA_MAX) {
      this.effort += SConf.getInstance().EFFORT_INC;
      this.effort = Math.min(this.getPConf().EFFORT_MAX, this.effort);
    }

    // restore stamina according to recovery value
    this.stamina = Math.min(SConf.getInstance().STAMINA_MAX,
        this.stamina + this.recovery * this.getPConf().STAMINA_INC_MAX);

    // END MT Alg. 3.1: stamina model algorithm
  }

  /**
   * predicts the player when his action is based on a given state.
   * 
   * @param state -
   *          the state
   */
  public void predictThisAfterState(final AbstractState state) {

    state.calculateMainAction();

    this.predictThisAfterAction(state.getMainAction());
    this.lastState = state.getState();

    if (this.isMe) {
      state.calculateTurnNeckAction();
      state.calculateViewModeAction();
      this.predictThisAfterAction(state.getTurnNeckAction());
      this.predictThisAfterAction(state.getViewModeAction());
    }

  }

  /**
   * To avoid castings, when an action is known to be a pointo-action, use this
   * method instead!
   * 
   * @param a -
   *          the dashaction
   */
  private void predictThisAfterPointToAction(final PointToAction a) {

    if (a == null) {
      return;
    }

    final double dir = a.getDir();

    if ((dir > 180) || (dir < -180)) {
      return;
    }

    this.setPointDirection(dir);

  }

  /**
   * To avoid castings, when an action is known to be a dash-action, use this
   * method instead!
   * 
   * @param a -
   *          the dashaction
   */
  private void predictThisAfterDashAction(final DashAction a) {

    if (a == null) {
      return;
    }

    double power = a.getPower();
    if (power > SConf.getInstance().MAXPOWER) {
      power = SConf.getInstance().MAXPOWER;
    }
    else if (power < SConf.getInstance().MINPOWER) {
      power = SConf.getInstance().MINPOWER;
    }
    double accel = power * this.getPConf().DASH_POWER_RATE * this.getEffort();
    // is this needed?
    if (accel > this.getPConf().PLAYER_ACCEL_MAX) {
      accel = this.getPConf().PLAYER_ACCEL_MAX;
    }

    this.dummyVektor.pointAtPolar(accel, this.getBodyDir());
    this.getSpeed().addToThis(this.dummyVektor);

    if (this.getSpeed().getLength() > this.getPConf().PLAYER_SPEED_MAX) {
      this.getSpeed().setLength(this.getPConf().PLAYER_SPEED_MAX);
    }

    this.extrapolate(1);

    // BEGIN reducing stamina according to
    // MT Alg. 3.1: stamina model algorithm

    // reduce stamina according to dash power
    if (power < 0) {
      this.stamina -= 2 * Math.abs(power);
    }
    else {
      this.stamina -= power;
    }
  }

  /**
   * To avoid castings, when an action is known to be a turn-action, use this
   * method instead!
   * 
   * @param a -
   *          the turn-action
   */
  private void predictThisAfterTurnAction(final TurnAction a) {

    if (a == null) {
      return;
    }

    double turnAngle = a.getAngle();

    if (turnAngle > SConf.getInstance().MAXMOMENT) {
      turnAngle = SConf.getInstance().MAXMOMENT;
    }
    else if (turnAngle < SConf.getInstance().MINMOMENT) {
      turnAngle = SConf.getInstance().MINMOMENT;
    }

    final double turnAngleEff = turnAngle / (1.0 + this.getPConf().INERTIA_MOMENT * this
        .getSpeed().getLength());
    this.setBodyDir(Vektor.normalize(this.getBodyDir() + turnAngleEff));
    this.extrapolate(1);
  }

  /**
   * this method limits the dash power of a dash action to a certain
   * Stamina-Level.
   * 
   * @param action
   * @param level
   */
  public void correctDashPower(final Action action,
      final ControllerNumbers.STAMINA_LEVEL level) {

    if (action.getType() != Action.TYPE.DASH) {
      return;
    }

    final DashAction dash = (DashAction) action;

    double stamina_thr = 0.0;
    switch (level) {
      case MAX:
        stamina_thr = DAInamiteConstants.STAMINA_THR_MAX;
        break;
      case HIGH:
        stamina_thr = DAInamiteConstants.STAMINA_THR_HIGH;
        break;
      case MEDIUM:
        stamina_thr = DAInamiteConstants.STAMINA_THR_MID;
        break;
      case MEDIUM_LOW:
        stamina_thr = DAInamiteConstants.STAMINA_THR_MID_LOW;
        break;
      case LOW:
        stamina_thr = DAInamiteConstants.STAMINA_THR_LOW;
        break;
      case ZERO:
        stamina_thr = 0.0;
    }

    double maxDashPower;
    if (this.getStamina() > stamina_thr) {
      maxDashPower = this.getStamina() - stamina_thr + this.getPConf().STAMINA_INC_MAX * this
          .getRecovery() * 0.8;
    }
    else {
      maxDashPower = (this.getStamina() / SConf.getInstance().STAMINA_MAX) * (this
          .getPConf().STAMINA_INC_MAX * this.getRecovery()) * 0.8;
    }
    if (dash.getPower() < 0) {
      maxDashPower = -Math.min(-dash.getPower(), maxDashPower / 2.0);
    }
    else {
      maxDashPower = Math.min(dash.getPower(), maxDashPower);
    }
    dash.setPower((int) maxDashPower);
  }

  /**
   * ***************************************** Robo - Physics
   * ***************************************************
   */
  /**
   * This method calculates the actual power rate for a kick command considering
   * the ball's location relative to the player.
   * 
   * @param b -
   *          ball to kick
   * 
   * @return the actual power rate
   */
  public double getActualKickPowerRate(final Ball b) {

    final double distance = this.getDistance(b) - this.getPConf().PLAYER_SIZE - SConf
        .getInstance().BALL_SIZE;

    // ball seen inside the player (with noise)
    if (distance < -0.005) {
      // shouldn't occur when ball.updateBallWhenInsideMe() is used
      throw new RuntimeException(
          this.getCycle() + ". Player:" + this.getNumber() + " Ball seen inside Player in Player.getActualKickRate Ball:" + b
              .toString() + " P:" + this.getPosition().toString() + distance);
    }

    // can't kick
    if (distance > this.getPConf().KICKABLE_MARGIN + SConf.getInstance().MARGIN_NOISE) {
      throw new RuntimeException(
          this.getCycle() + ". Player:" + this.getNumber() + " " + b + " out of range in Player.getActualKickRate -> no kicking (try to correct canKick())" + distance + " " + this
              .getPConf().KICKABLE_MARGIN);
    }

    final double angle = Math.abs(this.getAngleRel(b, RELATIVE.TO_BODY));

    return SConf.getInstance().KICK_POWER_RATE * (1 - 0.25 * (angle / 180) - 0.25 * (distance / this
        .getPConf().KICKABLE_MARGIN));
  }

  /**
   * the max ball acceleration when kicking to a given point.
   * 
   * @param b -
   *          ball
   * @param posToKickTo -
   *          point to kick to
   * @return - max ball accel (-1 if not poss)
   */
  public double getMaxBallAccel(final Ball b,
      final Vektor posToKickTo) {

    this.dummyVektor.copy(posToKickTo);
    this.dummyVektor.subFromThis(b.getPosition());
    this.dummyVektor.setLength(1.0);

    return this.getMaxBallAccelOptimized(b, this.dummyVektor);
  }

  /**
   * the max ball acceleration given the normalized dir to kick to. (that's more
   * efficient as the Vektor-operations are prevented)
   * 
   * @param b -
   *          ball
   * @param dirToKickTo -
   *          abs. direction to kick to given as an Vektor of length=1
   * @return the max ballAcceleration
   */
  public double getMaxBallAccelOptimized(final Ball b,
      final Vektor dirToKickTo) {

    final double maxKickAccel = this.getActualKickPowerRate(b) * SConf.getInstance().MAXPOWER;

    double term = b.getSpeed().scalar(dirToKickTo);
    final double sqrt_term = maxKickAccel * maxKickAccel - Math.pow(b.getSpeed()
        .getLength(), 2) + Math.pow(term, 2);

    if (sqrt_term < 0) {
      return -1;
    }

    term += Math.sqrt(sqrt_term);

    if (term < 0.0) {
      return -1;
    }

    term = Math.min(SConf.getInstance().BALL_SPEED_MAX, term);

    return term;
  }

  /**
   * the max ballAccel as a Vektor.
   * 
   * @param b
   * @param posToKickTo
   * @return the max ballAcceleration
   */
  public Vektor getMaxBalAccelVektor(final Ball b,
      final Vektor posToKickTo) {

    this.dummyVektor.copy(posToKickTo);
    this.dummyVektor.subFromThis(b.getPosition());
    this.dummyVektor.setLength(1.0);

    this.dummyDouble = this.getMaxBallAccelOptimized(b, this.dummyVektor);
    if (this.dummyDouble <= 0) {
      return null;
    }

    return new Vektor(this.dummyDouble, this.dummyVektor.getAngle());
  }

  /**
   * ***************************************** simple Getter/Setter
   * *********************************************
   */

  /**
   * Determines if this player is me or any player.
   * 
   * @return true if this player is me, false if not.
   */
  public final boolean isMe() {

    return this.isMe;
  }

  /**
   * @return true if the player is a teammate, else false
   */
  public final boolean isFriend() {

    return this.isFriend;
  }

  /**
   * Player is goalie
   * 
   * @author rw
   * @return true if the player is goalie
   */
  public final boolean isGoalie() {

    return this.isGoalie;
  }

  /**
   * @return Returns the bodyDir.
   */
  public final double getBodyDir() {

    return this.bodyDir;
  }

  /**
   * @return Returns the headDir.
   */
  public final double getHeadDir() {

    return Vektor.normalize(this.getBodyDir() + this.getNeckAngle());
  }

  /**
   * @return Returns the number.
   */
  public final double getNeckAngle() {

    return this.neckAngle;
  }

  /**
   * @return Returns the number.
   */
  public final int getNumber() {

    return this.number;
  }

  /**
   * @return double - the current player's stamina
   */
  public final double getStamina() {

    return this.stamina;
  }

  /**
   * @return double - the current player's effort
   */
  public final double getEffort() {

    return this.effort;
  }

  /**
   * @return double - the current player's stamina recovery rate
   */
  public final double getRecovery() {

    return this.recovery;
  }

  /**
   * @param bodyDir
   *          The bodyDir to set.
   */
  public void setBodyDir(final double bodyDir) {

    this.bodyDir = bodyDir;
  }

  /**
   * @param isFriend
   *          The isFriend to set.
   */
  public void setFriend(final boolean isFriend) {

    this.isFriend = isFriend;
  }

  /**
   * @param isMe
   *          The isMe to set.
   */
  public void setMe(final boolean isMe) {

    this.isMe = isMe;
  }

  /**
   * @param neckAngle
   *          The neckAngle to set.
   */
  public void setNeckAngle(final double neckAngle) {

    this.neckAngle = neckAngle;
  }

  /**
   * @param number
   *          The number to set.
   */
  public void setNumber(final int number) {

    this.number = number;
  }

  /**
   * @param stamina -
   *          the stamina to set
   */
  public void setStamina(final double stamina) {

    // if the new stamina differs much - mark agent as changed-status
    if (Math.abs(stamina - this.lastStaminaStatus) > DAInamiteConstants.STAMINA_STATUS_DIFF && this.isMe == false) {
      this.statusChanged = true;
      this.lastStaminaStatus = stamina;
    }
    this.stamina = stamina;
  }

  /**
   * @param effort -
   *          the effort to set
   */
  public void setEffort(final double effort) {

    this.effort = effort;
  }

  /**
   * @param recovery -
   *          the stamina recovery rate to set
   */
  public void setRecovery(final double recovery) {

    this.recovery = recovery;
  }

  /**
   * (non-Javadoc)
   * 
   * @see java.lang.Object#toString()
   */
  @Override
  public String toString() {

    return "Player C:" + this.getCycle() + " BC:" + this.getBodyCycle() + ". Nr:" + this
        .getNumber() + " Pos:" + this.getPosition() + " Vel:" + this.getSpeed() + " HA:" + this
        .getHeadDir() + " BA:" + this.getBodyDir() + " ME:" + this.isMe + " Friend:" + this.isFriend + " Reliable:" + this
        .isReliable();
  }

  /**
   * @param isGoalie
   *          The isGoalie to set.
   */
  public void setGoalie(final boolean isGoalie) {
    
    this.isGoalie = isGoalie;
    if (!this.isFriend)
    	this.pConf = new PConf(); // reset to standard
  }

  /**
   * players pConf.
   * 
   * @return the players configuration
   */
  public PConf getPConf() {

    if (this.pConf == null) {
      this.pConf = new PConf();
    }
    return this.pConf;
  }

  /**
   * set players pConf. (May be used for het. players)
   * 
   * @param conf
   */
  public void setPConf(final PConf conf) {

    this.pConf = conf;
    if (conf != null) {
      PLAYER_DEC_AFTER_N[0]= 1;
      REL_PLAYER_DIST_AFTER_N[0]= 0;
      for (int i= 1; i < 50; i++) {
        PLAYER_DEC_AFTER_N[i]= PLAYER_DEC_AFTER_N[i-1] * this.pConf.PLAYER_DECAY;
        REL_PLAYER_DIST_AFTER_N[i]= REL_PLAYER_DIST_AFTER_N[i-1]+ PLAYER_DEC_AFTER_N[i-1]; 
      }      
    }
  }

  /**
   * a players kick distance
   * 
   * @return a players kick distance
   */
  public double getKickDistance() {

    return this.pConf.KICKABLE_MARGIN + this.pConf.PLAYER_SIZE + SConf
        .getInstance().BALL_SIZE;
  }

  /**
   * a players catch distance
   * 
   * @return a players catch distance (0 if no goalie)
   */
  public double getCatchDistance() {

    if (!this.isGoalie) {
      return 0.0;
    }
    return SConf.getInstance().CATCHABLE_AREA_L + SConf.getInstance().BALL_SIZE;
  }

  /**
   * the default role based on the players number.
   * 
   * @return the default role of the player
   */
  public ROLE getDefaultRole() {

    return this.getRole(this.number);
  }

  /**
   * @return the role based on the players ROLE(!) number
   */
  public ROLE getRoleByRoleNumber() {

    return this.getRole(this.roleNumber);
  }

  /**
   * Returns the default role for the player number
   * 
   * @param playernumber
   *          the number of the player
   * @return the default role
   */
  private ROLE getRole(final int playernumber) {

    if (playernumber < 1 || playernumber > 11) {
      return ROLE.UNKNOWN;
    }

    return this.numberToRoles[playernumber - 1];
  }

  /**
   * the max turning angle turning 1x given the players speed and
   * INERTIA_MOMENT.
   * 
   * @return the max turn angle after 1 turn
   */
  public double getMaxTurningAngle1() {

    return 180.0 / (1 + this.getSpeed().getLength() * this.getPConf().INERTIA_MOMENT);
  }

  /**
   * the max turning angle turning 2x given the players speed and
   * INERTIA_MOMENT.
   * 
   * @return the max turn angle after 2 turns
   */
  public double getMaxTurningAngle2() {

    return this.getMaxTurningAngle1() + 180.0 / (1 + this.getSpeed()
        .getLength() * this.getPConf().PLAYER_DECAY * this.getPConf().INERTIA_MOMENT);
  }

  /**
   * This method calculates the angle that has to be given to a turn command in
   * order to turn the body by a given angle.
   * 
   * @param angle
   *          The angle the player is supposed to turn his body by.
   * @return double
   */
  public double getAngleForTurn(final double angle) {

    final double speed = this.getSpeed().getLength();
    double turnAngle = angle * (1.0 + this.getPConf().INERTIA_MOMENT * speed);
    if (turnAngle > SConf.getInstance().MAXMOMENT) {
      turnAngle = SConf.getInstance().MAXMOMENT;
    }
    if (turnAngle < SConf.getInstance().MINMOMENT) {
      turnAngle = SConf.getInstance().MINMOMENT;
    }

    return turnAngle;
  }

  /**
   * Succ_Prob=1 - ( player_2_ball.x / tackle_dist ) exp 6 + ( abs (
   * player_2_ball.y ) / 1.25 ) exp 6 where tackle_dist is 2.0 when the ball is
   * in front of the player, 0.5 otherwise.
   * 
   * player_2_ball is the vector from the player to the ball relative to the
   * player's body direction.
   * 
   * @param ballPosition
   *          the position of the ball
   * @return the probability that tackle action will work
   */
  public final double tackleSuccessProbability(final Vektor ballPosition) {

    return 1 - this.tackleFailureProbability(ballPosition);
  }

  /**
   * Fail_Prob=( player_2_ball.x / tackle_dist ) exp 6 + ( abs ( player_2_ball.y ) /
   * 1.25 ) exp 6 where tackle_dist is 2.0 when the ball is in front of the
   * player, 0.5 otherwise.
   * 
   * player_2_ball is the vector from the player to the ball relative to the
   * player's body direction.
   * 
   * @param ballPosition
   *          the position of the ball
   * @return the probability that tackle action will fail
   */
  public final double tackleFailureProbability(final Vektor ballPosition) {

	if (Math.abs(this.getAngleRel(ballPosition, RELATIVE.TO_BODY)) > 90) {
		return 1;
	}
	
    // player_2_ball is the vector from the player to the ball relative to
    // the player's body direction.
    final Vektor player2Ball = this.position.getVektorTo(ballPosition);
    
    player2Ball.rotate(-this.bodyDir);

    final SConf sconf = SConf.getInstance();

    // Check if ball is out of tackle range then stop calculation
    if (player2Ball.y > sconf.TACKLE_DIST || player2Ball.y < -sconf.TACKLE_BACK_DIST || player2Ball.x > sconf.TACKLE_WIDTH || player2Ball.x < -sconf.TACKLE_WIDTH) {
      return 1;
    }

    // ATTENTION, X and Y must stay swapped because of our coordinate system
    final double result = Math
        .pow(
            player2Ball.y / (player2Ball.y > 0 ? sconf.TACKLE_DIST : sconf.TACKLE_BACK_DIST),
            sconf.TACKLE_EXPONENT) + Math.pow(
        Math.abs(player2Ball.x) / sconf.TACKLE_WIDTH, sconf.TACKLE_EXPONENT);

    // Sometimes probability is a bit greater than 1,
    // and may be smaller than 0, if tackle_exponent is odd number,
    // just to be sure
    if (result > 1) {
      return 1;
    }
    else if (result < 0) {
      return 0;
    }
    else {
      return result;
    }
  }

  /**
   * @return the tackledeadCountdown.
   */
  public int getTackledeadCountdown() {

    return this.tackledeadCountdown;
  }

  /**
   * Tells if the player has the ball in its kickable margin with noise as
   * security.
   * 
   * @param b -
   *          ball
   * @return true if kickable and not tackle dead and ball reliable
   */
  public boolean canKick(final Ball b) {

    return this.canKick(b, 0);// SConf.getInstance().MARGIN_NOISE);
  }

  /**
   * tells if the player has the ball in its kickable margin
   * 
   * @param b
   *          the ball
   * @param security
   *          kickable area will be reduced by security when determining if the
   *          ball is kickable
   * @return true if kickable and not tackle dead and ball reliable
   */
  public boolean canKick(final Ball b,
      final double security) {

    return this.tackledeadCountdown == 0 && b.isReliable() && this
        .getKickDistance() - security > this.getDistance(b);
  }

  /**
   * <code>canCatch</code> checks if player can catch the ball from his
   * current position Based on MT 3.4.6
   * 
   * @param b
   *          the ball
   * @param world
   *          the world
   * @return true if the goalie can catch the ball
   * 
   */
  public final boolean canCatch(final Ball b,
      final WorldModel world) {
	  
	  if(!this.isGoalie)
		  return false;
	  
	  Ball ballRef = world.getBallRef();

	    if (ballRef.getAge(this.getBodyCycle()) > 2) {
	      return false;
	    }
	    if (!world.inOwnPenaltyArea(ballRef)) {
	      return false;
	    }
	    if (this.getCatchDistance() - CATCH_SECURITY_DIST < ballRef
	        .getDistance(this)) {
	      return false;
	    }
	    return true;

	  // OLD VERSION
//	  
//    // TODO Smarqoo, Jan 21, 2005, 11:51:06 PM, check what means "ball
//    // inside catchable area"
//    // does it mean the position of the ball, or the whole ball is inside
//    // (this version)?
//    // TODO Smarqoo, Jan 21, 2005, 11:59:06 PM, check ban cycles?
//    if (this.isGoalie() && b.isReliable() && world.inOwnPenaltyArea(b) && this
//        .getTackledeadCountdown() == 0) {
//      // angle between ball & BodyDir
//      final Vektor toBall = b.getPosition().sub(this.getPosition());
//      final double catchAngle = Vektor.normalize(toBall.getAngle() - this
//          .getBodyDir());
//      final double catchL = SConf.getInstance().CATCHABLE_AREA_L - SConf
//          .getInstance().MARGIN_NOISE;
//      // CATCHABLE_AREA_L first
//      if (toBall.getLength() + SConf.getInstance().BALL_SIZE > catchL) {
//        // ball further than we can reach
//        return false;
//      } // end if
//
//      // check if catchAngle between MAX-/MIN- MOMENT
//      if ((catchAngle > 0 && catchAngle < SConf.getInstance().CATCH_MAXMOMENT) || (catchAngle < 0 && catchAngle > SConf
//          .getInstance().CATCH_MINMOMENT)) {
//        // in this case, we don't have to check the CATCHABLE_AREA_W,
//        // we can catch in the middle of the catchable area
//        return true;
//      } // end if
//
//      // now the tricky part, CATCHABLE_AREA_W needs to be checked
//      // let's do it using sinus, we need the angle first
//      final double catchW = SConf.getInstance().CATCHABLE_AREA_W - SConf
//          .getInstance().MARGIN_NOISE;
//      double alpha = (catchAngle > SConf.getInstance().CATCH_MAXMOMENT) ? catchAngle - SConf
//          .getInstance().CATCH_MAXMOMENT : catchAngle - SConf.getInstance().CATCH_MINMOMENT;
//      alpha = Math.abs(alpha);
//      // okay, let's go
//      final double deltaW = Math.sin(Math.toRadians(alpha)) * toBall
//          .getLength();
//      // we only need to substract now
//      if ((deltaW + SConf.getInstance().BALL_SIZE) <= catchW) {
//        return true;
//      } // end if
//    }
//
//    return false;
  } // end of method canCatch()

  /**
   * @param tackledeadCountdown
   *          The tackledeadCountdown to set.
   */
  public void setTackledeadCountdown(final int tackledeadCountdown) {

    this.tackledeadCountdown = tackledeadCountdown;
  }

  /**
   * adds a state to the player.
   * 
   * @param state -
   *          the state to add
   * @param playModes -
   *          the play modes when the state is valid
   */
  public void addKnownState(final STATES state,
      final LinkedList<PLAY_MODE> playModes) {

    if (this.knownStates.containsKey(state)) {
      System.out
          .println("Player " + this.getNumber() + " State already contained. " + state
              .toString());
      return;
    }

    this.knownStates.put(state, playModes);
  }

  /**
   * adds a playmode to the list.
   * 
   * @param state -
   *          state to add the play mode to
   * @param mode -
   *          play mode to add
   */
  public void addPlayModeToState(final STATES state,
      final PLAY_MODE mode) {

    LinkedList<PLAY_MODE> modes = this.getPlayModes(state);
    if (modes == null) {
      modes = new LinkedList<PLAY_MODE>();
      modes.add(mode);
      this.addKnownState(state, modes);
    }
    else {
      modes.add(mode);
    }
  }

  /**
   * all play_modes to a given state
   * 
   * @param state
   * @return play modes
   */
  public LinkedList<PLAY_MODE> getPlayModes(final STATES state) {

    return this.knownStates.get(state);
  }

  /**
   * player contains the state
   * 
   * @param state
   * @return true if contained
   */
  public boolean knowsState(final STATES state) {

    return this.knownStates.containsValue(state);
  }

  /**
   * returns the best state for a player based on the worlmodel and a given
   * ball.
   * 
   * @param ball
   */
  public void calculateBestState(final Ball ball) {

    if (this.myStateEval == null) {
      return;
    }

    this.myStateEval.calculateBestState(this, ball);
  }

  /**
   * @return Returns the myStateEval.
   */
  public StateEvaluation getMyStateEval() {

    return this.myStateEval;
  }

  /**
   * @param myStateEval
   *          The myStateEval to set.
   */
  public void setMyStateEval(final StateEvaluation myStateEval) {

    this.myStateEval = myStateEval;
  }

  /**
   * @return Returns the lastState.
   */
  public STATES getLastState() {

    return this.lastState;
  }

  /**
   * @deprecated
   */
  @Deprecated
  public double getPointDirection() {

    return this.pointDirection;
  }

  /**
   * @deprecated
   */
  @Deprecated
  public void setPointDirection(final double pointDirection) {

    this.pointDirection = pointDirection;
  }

  /**
   * @param rel
   *          relative to body or head?
   * @return returns the armDirection relative to body or head.
   */
  public double getArmDirection(final RELATIVE rel) {

    if (rel == RELATIVE.TO_HEAD) {
      return Vektor.normalize(this.armDirection - this.getBodyDir() - this
          .getNeckAngle());
    }
    // RELATIVE.TO_BODY
    return Vektor.normalize(this.armDirection - this.getBodyDir());
  }

  /**
   * This method calculates the power that should be given to a dash command in
   * order to come as close to the position as possible.
   * 
   * @param position
   *          absolute position
   * @return the power for the dash command
   */
  public int getPowerForDash(final Vektor position) {

    this.dummyVektor.copy(position);
    this.dummyVektor.subFromThis(this.getPosition());
    this.dummyVektor.subFromThis(this.getSpeed());

    this.dummyVektor2.pointAtPolar(1.0, this.getBodyDir());
    final double d = this.dummyVektor.scalar(this.dummyVektor2);
    final double dashParameter = d / (this.getPConf().DASH_POWER_RATE * this
        .getEffort());

    return this.correctDashPowerByLimits(dashParameter);
  }

  /**
   * This method calculates the power that should be given to a dash command in
   * order to come as close to the position as possible. it differs from the
   * simple version of this method in that the drift is considered. (i.e.: if
   * you wan't to be at a position in 2 cycles it doesn't make sense to dash
   * full first and stop afterwards (looses 1 action) instead the speed
   * resulting from dashing + the drift in the next cycle should bring you to
   * the position)
   * 
   * @param position
   *          the position
   * @param time -
   *          time when to arrive at position
   * @return the power of the dash
   */
  public int getPowerForDash(final Vektor position,
      final int time) {

    if (time == 1) {
      return this.getPowerForDash(position);
    }

    if (time < 1) {
      throw new RuntimeException("got wrong time:" + time);
    }

    // Vektor from nextPlayerPos to position
    this.dummyVektor.copy(position);
    this.dummyVektor.subFromThis(this.getPosition());
    this.dummyVektor2.copy(this.getSpeed());
    this.dummyVektor2.multThis(relativePlayerMovementAfterNCycles(time));
    this.dummyVektor.subFromThis(this.dummyVektor2);

    // Einheitsvektor in body dir.
    this.dummyVektor2.pointAtPolar(1.0, this.getBodyDir());

    final double d = this.dummyVektor.scalar(this.dummyVektor2);
    double dashParameter = d / (this.getPConf().DASH_POWER_RATE * this
        .getEffort());
    dashParameter /= RCMath.calculateRow(this.getPConf().PLAYER_DECAY, time);

    return this.correctDashPowerByLimits(dashParameter);
  }

  /**
   * correct the dash power if dashing brings us about the max speed. This
   * should occure only for some het. players. Saves up to 40% stamina!!
   * 
   * @param power -
   *          the dash power (-100;100)
   * @return the corrected dash power
   */
  private int correctDashPowerByLimits(double power) {

    if (power > SConf.getInstance().MAXPOWER) {
      power = SConf.getInstance().MAXPOWER;
    }
    if (power < -SConf.getInstance().MAXPOWER) {
      power = -SConf.getInstance().MAXPOWER;
    }

    final double speed = this.getSpeed().getLength();

    // added some security
    final double resAccel = Math.abs(power * this.getPConf().DASH_POWER_RATE * this
        .getEffort() * 0.95);
    final double resSpeed = speed + resAccel;

    if (resSpeed > this.getPConf().PLAYER_SPEED_MAX) {

      power *= (resAccel - (resSpeed - this.getPConf().PLAYER_SPEED_MAX)) / resAccel;
    }

    return (int) power;
  }

  /**
   * This method retrieves the absolute armDirection the agent is pointing to!
   * 
   * @return the absolute angle
   */
  public double getAbsoluteArmDirection() {

    return Vektor.normalize(this.armDirection);
  }

  /**
   * @param armDirection
   *          The armDirection to set.
   */
  public void setArmDirection(final double armDirection) {

    this.armDirection = armDirection;
  }

  /**
   * @return Returns the armDistance.
   */
  public double getArmDistance() {

    return this.armDistance;
  }

  /**
   * @param armDistance
   *          The armDistance to set.
   */
  public void setArmDistance(final double armDistance) {

    this.armDistance = armDistance;
  }

  /**
   * @return Returns the armExpires.
   */
  public int getArmExpires() {

    return this.armExpires;
  }

  /**
   * @param armExpires
   *          The armExpires to set.
   */
  public void setArmExpires(final int armExpires) {

    this.armExpires = armExpires;
  }

  /**
   * @return Returns the armMovable.
   */
  public boolean isArmMovable() {

    return this.armMovable;
  }

  /**
   * @param armMovable
   *          The armMovable to set.
   */
  public void setArmMovable(final boolean armMovable) {

    this.armMovable = armMovable;
  }

  /**
   * @return true if the status was changed
   */
  public boolean isStatusChanged() {

    return this.statusChanged;
  }

  /**
   * @param statusChanged
   *          set the statuschange
   */
  public void setStatusChanged(final boolean statusChanged) {

    this.statusChanged = statusChanged;
  }

  /**
   * sets the message type
   * 
   * @param bodyCycle
   *          the bodycycle of the player
   * 
   * @param number
   * @param messageType
   */
  public void setLastHeared(final int bodyCycle,
      final int number,
      final MESSAGES messageType) {

    this.lastHearedInfo.bodyCycle = bodyCycle;
    this.lastHearedInfo.setNumber(number);
    this.lastHearedInfo.setMessageType(messageType);
  }

  /**
   * 
   * @return the body cycle of the last hear message
   */
  public int getLastHearBodyCycle() {

    return this.lastHearedInfo.bodyCycle;
  }

  /**
   * 
   * @return the last heared player number
   */
  public int getLastHearedPlayer() {

    return this.lastHearedInfo.getNumber();
  }

  /**
   * 
   * @return the last heared message type
   */
  public MESSAGES getLastHearedMessageType() {

    return this.lastHearedInfo.messageType;
  }

  /**
   * @return Returns the roleNumber.
   */
  public int getRoleNumber() {

    if (this.roleNumber < 0 && this.isFriend) {
      return this.number;
    }

    return this.roleNumber;
  }

  /**
   * @param roleNumber
   *          The roleNumber to set.
   */
  public void setRoleNumber(final int roleNumber) {

    this.roleNumber = roleNumber;
  }

  /**
   * @return Returns the ballPossessions.
   */
  public RingList<TEAM> getBallPossessions() {

    return this.ballPossessions;
  }

  /**
   * @return Returns the wasOffsideWhenPassed.
   */
  public boolean wasOffsideWhenPassed() {

    return this.wasOffsideWhenPassed;
  }

  /**
   * @param wasOffsideWhenPassed
   *          The wasOffsideWhenPassed to set.
   */
  public void setWasOffsideWhenPassed(final boolean wasOffsideWhenPassed) {

    this.wasOffsideWhenPassed = wasOffsideWhenPassed;
  }

  /**
   * @return Returns the kickedStandard.
   */
  public boolean kickedStandard() {

    return this.kickedStandard;
  }

  /**
   * @param kickedStandard
   *          The kickedStandard to set.
   */
  public void setKickedStandard(final boolean kickedStandard) {

    this.kickedStandard = kickedStandard;
  }

  /**
   * Calculates the decay that is used to calculate the ball speed after n cycles. (Equals Math.pow(player_dec,n) but is faster)
   * 
   * @param n the number of cycles
   * @return the decay for n cycles 
   */
  public final double playerDecayAfterNCycles(final int n) {
    if (n < 0) {
      throw new IllegalArgumentException("no player decay for "+n+" cycles");
    }
    if (n == 0) {      
      return 1;
    }
    if (n > 50) {
      return playerDecayAfterNCycles(n-1)*pConf.PLAYER_DECAY;
    }
    return PLAYER_DEC_AFTER_N[n];
  }

  /**
   * Calculates the players movement after n cycles starting with speed 1.
   * So for cycle 0 there is no movement. For cycle 1 the movement is 1 and that continues in a geometric row.
   * 
   * @param n the number of cycles
   * @return the relative players movement after n cycles 
   */
  public final double relativePlayerMovementAfterNCycles(final int n) {
    if (n < 0) {
      throw new IllegalArgumentException("no player movement for "+n+" cycles");
    }
    if (n > 50) {
      return relativePlayerMovementAfterNCycles(n-1)+playerDecayAfterNCycles(n-1);
    }
    return REL_PLAYER_DIST_AFTER_N[n];
  }
  


  /**
   * Extrapolates this Player based on its speed.
   * 
   * @param n -
   *            horizont (cycles)
   * 
   */
  public void extrapolate(final int n) {
    // as profiling shows that Math.pow is quiet slow
    // the common case of extrapoling by 1 cycle
    // is handled in a more efficient way
    if (n == 0)
      return;
    if (n == 1) {
      this.position.x += this.speed.x;
      this.position.y += this.speed.y;
      this.speed.x *= this.getPConf().PLAYER_DECAY;
      this.speed.y *= this.getPConf().PLAYER_DECAY;
    } else {
      this.position.x += this.speed.x
          * relativePlayerMovementAfterNCycles(n);
      this.position.y += this.speed.y
          * relativePlayerMovementAfterNCycles(n);
      this.speed.x *= playerDecayAfterNCycles(n);
      this.speed.y *= playerDecayAfterNCycles(n);
    }
    // no stamina update as the main actions are missing
  }
}
